/**
 */

#include "mem_pool_manager.h"
#include "mem_allocator.h"

#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <limits>

namespace MemTest {

MemPoolManager::MemPoolManager(const std::string& in_name, int8_t* const in_addr, uint32_t in_unit_size,
                               uint32_t in_unit_count)
    : pool_name_(in_name),
      pool_addr_(in_addr),
      unit_size_(in_unit_size),
      unit_count_(in_unit_count),
      free_count_(unit_count_),
      alloc_call_count_(0),
      return_null_count_(0),
      request_too_big_(0),
      in_use_(in_unit_count, false),
      next_free_(in_unit_count),
      first_free_(0),
      last_free_(in_unit_count - 1) {
  uint32_t free_index = 0;
  for (; free_index < in_unit_count; free_index++) {
    next_free_[free_index] = free_index + 1;
  }

  MY_LOG_INFO(
      "MemPoolManager [%s] %p created pool=%p, "
      "unit_size=%u, unit_count=%u, allocator=%s",
      pool_name_.c_str(), this, pool_addr_, unit_size_, unit_count_,
      MemAllocDelegate::get_instance()->get_allocator_name(pool_addr_));
}

MemPoolManager::~MemPoolManager() {
  const std::lock_guard<std::mutex> lk(pool_manager_mutex_);
  in_use_.clear();
  next_free_.clear();
}

int8_t* MemPoolManager::mem_alloc(uint32_t mem_size) {
  int8_t* return_addr = nullptr;
  const std::lock_guard<std::mutex> lk(pool_manager_mutex_);
  alloc_call_count_++;

  if (mem_size > unit_size_) {
    request_too_big_++;
    return_null_count_++;
  } else {
    if (free_count_ == 0) {
      return_null_count_++;
    } else {
      const uint32_t first_one = first_free_;
      MY_LOG_DEBUG("first_one=%u", first_one);
      if (first_one >= next_free_.size()) {
        MY_LOG_PANIC("first_one=%u, next_free_.size()=%lu", first_one, next_free_.size());
      }
      first_free_ = next_free_[first_one];
      MY_LOG_DEBUG("first_free_=%u", first_free_);
      next_free_[first_one] = std::numeric_limits<uint32_t>::max();

      if (last_free_ == first_one) {
        last_free_ = std::numeric_limits<uint32_t>::max();
      }

      in_use_[first_one] = true;
      return_addr = std::next(pool_addr_, unit_size_ * static_cast<uint32_t>(first_one));
      free_count_--;
    }
  }

  return return_addr;
}

void MemPoolManager::mem_free(const int8_t* const in_addr) {
  const std::lock_guard<std::mutex> lck(pool_manager_mutex_);

  const uint32_t offset_size = static_cast<uint32_t>(pool_addr_ - in_addr);
  // const uint32_t offset_size = pool_addr_ - in_addr;
  const uint32_t use_idx = offset_size / unit_size_;
  in_use_[use_idx] = false;

  const uint32_t last_one = last_free_;
  if (last_one == std::numeric_limits<uint32_t>::max()) {
    last_free_ = use_idx;
    first_free_ = use_idx;
  } else {
    next_free_[last_one] = use_idx;
    last_free_ = use_idx;
  }

  free_count_++;
}

MemPool::MemPool(const std::string& in_name, uint32_t in_unit_sz, uint32_t in_unit_nm, uint64_t in_alignment,
                 bool in_is_sys_malloc)
    : is_sys_malloc_(in_is_sys_malloc) {
  if (in_unit_sz == 0) {
    MY_LOG_ERROR("%s unit_size = %u", in_name.c_str(), in_unit_sz);
  }
  if (in_unit_nm == 0) {
    MY_LOG_ERROR("%s unit_number = %u", in_name.c_str(), in_unit_nm);
  }
  if (in_alignment == 0) {
    MY_LOG_ERROR("%s alignment = %lu", in_name.c_str(), in_alignment);
  }
  if ((in_alignment & (in_alignment - 1)) != 0) {
    MY_LOG_ERROR("%s alignment = %lu", in_name.c_str(), in_alignment);
  }

  const uint64_t tmp_val1 = static_cast<uint64_t>(in_unit_sz) - 1;
  const uint64_t tmp_val2 = (tmp_val1 / in_alignment + 1) * in_alignment;

  unit_size_ = static_cast<uint32_t>(tmp_val2);

  unit_count_ = in_unit_nm;
  alignment_size_ = in_alignment;
  alloc_delegate_ = MemAllocDelegate::get_instance();
  pool_name_ = in_name;
  MY_LOG_INFO("pool_name_=%s", pool_name_.c_str());

  if (is_sys_malloc_ == true) {
    pool_addr_ = alloc_delegate_->mem_calloc(unit_count_ + 1, unit_size_, true);
  } else {
    pool_addr_ = alloc_delegate_->mem_calloc(unit_count_ + 1, unit_size_, false);
  }
  if (pool_addr_ == nullptr) {
    MY_LOG_ERROR("%s cannot alloc memory %u %u", in_name.c_str(), in_unit_sz, in_unit_nm);
  } else {
    aligned_pool_ = get_aligned_address(pool_addr_, in_alignment);
    pool_manager_ptr_ = std::make_shared<MemPoolManager>(pool_name_, aligned_pool_, unit_size_, unit_count_);
    if (pool_manager_ptr_ == nullptr) {
      MY_LOG_ERROR("%s cannot alloc manager", in_name.c_str());
    }
  }
}

MemPool::~MemPool() {
  alloc_delegate_->mem_free(pool_addr_);
}

int8_t* MemPool::mem_alloc() const {
  return pool_manager_ptr_->mem_alloc(unit_size_);
}

void MemPool::mem_free(const int8_t* const in_addr) const {
  pool_manager_ptr_->mem_free(in_addr);
}

int8_t* MemPool::get_aligned_address(const int8_t* const in_address, uint64_t in_alignment_size) {
  int8_t* out_addr = nullptr;
  const uintptr_t in_addr_as = reinterpret_cast<uintptr_t>(in_address);
  const uintptr_t in_alignment_size_as = static_cast<uintptr_t>(in_alignment_size);

  if (in_addr_as % in_alignment_size_as != 0) {
    const uintptr_t offset = in_alignment_size_as - (in_addr_as % in_alignment_size_as);
    const uintptr_t tmp_in_addr_as = in_addr_as + offset;
    out_addr = reinterpret_cast<int8_t*>(tmp_in_addr_as);
  } else {
    out_addr = reinterpret_cast<int8_t*>(in_addr_as);
  }
  return out_addr;
}

}  // namespace MemTest
